package ch.sfug.controls {
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;	

	/**
	 * @author loop
	 */
	public class PanningViewport {

		private var vp:Rectangle;
		private var t:DisplayObject;
		private var sprite:Sprite;
		private var sensitivity:Number = 2 / 3;
		public var speed:Number = 3;

		public function PanningViewport( viewport:Rectangle = null, target:DisplayObject = null ) {
			this.viewport = viewport;
			this.target = target;
			sprite = new Sprite();
		}

		
		/**
		 * enables/disables the panning
		 */
		public function set enable( b:Boolean ):void {
			if( b ) {
				if( t != null ) {
					sprite.addEventListener(Event.ENTER_FRAME, update);
					t.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
				} else {
					throw new Error("can't start panning if there is no target to pan");
				}
			} else {
				sprite.removeEventListener(Event.ENTER_FRAME, update);
				t.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
			}
		}

		
		/**
		 * catches the event of the mouse
		 */
		private function onMouseWheel( me:MouseEvent ):void {
			t.y = Math.min(vp.y, Math.max(vp.bottom - t.height, t.y + ( me.delta * speed )));
		}

		
		/**
		 * catches the event when the mouse is moving
		 */
		protected function update( e:Event ):void {
			var hw:Number = vp.width / 2;
			var hh:Number = vp.height / 2;
			var dx:Number = t.stage.mouseX - ( vp.x + hw );
			var dy:Number = t.stage.mouseY - ( vp.y + hh );
			if( vp.width > t.width ) {
				t.x = ( vp.width - t.width ) / 2;
			} else {
				t.x = Math.min(vp.x, Math.max(vp.right - t.width, t.x - getSpeed(dx, hw)));
			}
			if( vp.height > t.height ) {
				t.y = ( vp.height - t.height ) / 2;
			} else {
				t.y = Math.min(vp.y, Math.max(vp.bottom - t.height, t.y - getSpeed(dy, hh)));
			}
		}

		
		/**
		 * returns the speed to add depending on the delta and height
		 */
		protected function getSpeed( d:Number, length:Number ):Number {
			var dir:Number = ( d < 0 ) ? -1 : 1;
			d = Math.abs(d);
			var opposite:Number = ( 1 - sensitivity ) * length;
			var move:Number = Math.max(0, Math.min(opposite, d - ( sensitivity * length ))) / opposite;
			return dir * move * speed;
		}

		
		/**
		 * gets the viewport rectangle which represents the visible content
		 */
		public function get viewport(  ):Rectangle {
			return vp;
		}

		
		/**
		 * returns the moving displayobject in the viewport
		 */
		public function get target(  ):DisplayObject {
			return t;
		}

		
		/**
		 * sets the viewport rectangle which represents the visible content
		 */
		public function set viewport( rec:Rectangle ):void {
			this.vp = rec;
		}

		
		/**
		 * sets the displayobject that is moved in the viewport
		 */
		public function set target( d:DisplayObject ):void {
			t = d;
		}
	}
}
